﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;


public class SpriteToSheetMap : Dictionary<string, string>
{
    public static SpriteToSheetMap CreateFromBinary(byte[] data)
    {
        MemoryStream stream = new MemoryStream(data);
        BinaryReader reader = new BinaryReader(stream);
        SpriteToSheetMap map = new SpriteToSheetMap();
        map.Read(reader);
        return map;
    }

    public void Write(BinaryWriter writer)
    {
        writer.Write(this.Count);
        SpriteToSheetMap.Enumerator en = this.GetEnumerator();
        while (en.MoveNext())
        {
            writer.Write(en.Current.Key);
            writer.Write(en.Current.Value);
        }
    }

    public void Read(BinaryReader reader)
    {
        this.Clear();
        int count = reader.ReadInt32();
        for (int i = 0; i < count; i++)
        {
            string k = reader.ReadString();
            string v = reader.ReadString();
            this.Add(k, v);
        }
    }

    public byte[] toBinary()
    {
        MemoryStream stream = new MemoryStream();
        BinaryWriter writer = new BinaryWriter(stream);
        this.Write(writer);
        return stream.GetBuffer();
    }
}


public class SpriteSheetLoader
{
	public SpriteSheetLoader[] subLoaders;

	private string texPath;
	private float pixelsPerUnit;
	public Dictionary<string, SpriteInfo> spriteInfoMap = new Dictionary<string, SpriteInfo>();

	private System.WeakReference texRef;

    public static SpriteSheetLoader CreateFromBinary(byte[] data)
    {
        MemoryStream stream = new MemoryStream(data);
        BinaryReader reader = new BinaryReader(stream);
        return new SpriteSheetLoader(reader);
    }

	public SpriteSheetLoader()
	{

	}

	public SpriteSheetLoader(string texPath, float pixelsPerUnit, Dictionary<string, SpriteInfo> spriteInfoMap)
	{
		this.texPath = texPath;
		this.pixelsPerUnit = pixelsPerUnit;
		this.spriteInfoMap = spriteInfoMap;
	}

	public SpriteSheetLoader(BinaryReader reader)
	{
		this.Read(reader);
	}

	public void Write(BinaryWriter writer)
	{
		writer.Write(texPath);
		writer.Write(pixelsPerUnit);

		writer.Write(spriteInfoMap.Count);
		SortedDictionary<string, SpriteInfo> writeDic = new SortedDictionary<string, SpriteInfo>(spriteInfoMap);
		SortedDictionary<string, SpriteInfo>.Enumerator enumerator = writeDic.GetEnumerator();
		while(enumerator.MoveNext())
		{
			writer.Write(enumerator.Current.Key);
			enumerator.Current.Value.Write(writer);
		}
	}

	public void Write(StringWriter writer)
	{
		writer.Write(texPath);
		writer.Write(pixelsPerUnit);
		
		writer.Write(spriteInfoMap.Count);
		Dictionary<string, SpriteInfo>.Enumerator enumerator = spriteInfoMap.GetEnumerator();
		while(enumerator.MoveNext())
		{
			writer.Write(enumerator.Current.Key);
			enumerator.Current.Value.Write(writer);
		}
	}

	public void Read(BinaryReader reader)
	{
		texPath = reader.ReadString();
		pixelsPerUnit = reader.ReadSingle();

		spriteInfoMap.Clear();
		int count = reader.ReadInt32();
		for(int i = 0; i < count; i++)
		{
			string name = reader.ReadString();
			SpriteInfo spriteInfo = new SpriteInfo();
			spriteInfo.Read(reader);
			spriteInfoMap[name] = spriteInfo;
		}
	}

	Sprite realLoad (string spriteName, Texture2D tex)
	{
		Sprite sprite = null;
		string realSpriteName = spriteName.Replace ('/', ResourcesEx.dirSymbol);
		SpriteInfo info = new SpriteInfo ();
		if (spriteInfoMap.TryGetValue (realSpriteName, out info))
		{
			System.WeakReference spriteRef = info.spriteRef;
			if (spriteRef != null)
			{
				sprite = spriteRef.Target as Sprite;
				if (sprite != null)
				{
					return sprite;
				}
			}
			if (sprite == null)
			{
				sprite = Sprite.Create (tex, info.rect, info.pivod, pixelsPerUnit, 0, SpriteMeshType.FullRect, info.border);
				sprite.name = realSpriteName;
				if (sprite != null)
				{
					if (spriteRef == null)
					{
						spriteRef = new System.WeakReference (sprite);
					}
					else
					{
						spriteRef.Target = sprite;
					}
					info.spriteRef = spriteRef;
				}
			}
		}
		return sprite;
	}

	public SpriteInfo GetSpriteInfo(string spriteName, bool isPath = false)
	{
		if(isPath)
		{
			spriteName = spriteName.Replace('/', ResourcesEx.dirSymbol);
		}
		SpriteInfo info;
		spriteInfoMap.TryGetValue(spriteName, out info);
		return info;
	}

	private SpriteSheetLoader getLeafLoader(string spriteName)
	{
		string realSpriteName = spriteName.Replace ('/', ResourcesEx.dirSymbol);

		SpriteInfo info;
		if(this.subLoaders == null && !this.spriteInfoMap.TryGetValue(realSpriteName, out info))
		{
			return null;
		}

		if(this.subLoaders != null)
		{
			for(int i = 0; i < subLoaders.Length; i++)
			{
				if(subLoaders[i] == null)
				{
					continue;
				}
				
				SpriteSheetLoader loader = subLoaders[i].getLeafLoader(spriteName);
				if(loader != null)
				{
					return loader;
				}
			}
			return null;
		}
		else
		{
			return this;
		}
	}

	Texture2D getTex()
	{
		Texture2D tex = null;
		if(texRef != null)
		{
			tex = texRef.Target as Texture2D;
		}
		
		if(tex == null)
		{
//			tex = ResourcesEx.LoadTexture(texPath);
			if(tex == null)
			{
				//if(UnityEngine.Debug.enableLog)UnityEngine.Debug.LogWarning("sprite sheet loader: load texture failed");
				return null;
			}
			
			if(texRef == null)
			{
				texRef = new System.WeakReference(tex);
			}
			else
			{
				texRef.Target = tex;
			}
		}
		return tex;
	}

	void getTexAsync(AsyncCallBack<Texture2D> callBack)
	{
		Texture2D tex = null;
		if(texRef != null)
		{
			tex = texRef.Target as Texture2D;
		}
		
		if(tex == null)
		{
//			ResourcesEx.LoadTextureAsync(texPath, (tex2)=>
//			{
//				if(tex2 == null)
//				{
//					//if(UnityEngine.Debug.enableLog)UnityEngine.Debug.LogWarning("sprite sheet loader: load texture failed");
//					callBack(null);
//				}
//				
//				if(texRef == null)
//				{
//					texRef = new System.WeakReference(tex2);
//				}
//				else
//				{
//					texRef.Target = tex2;
//				}
//				callBack(tex2);
//			});
		}
		else
		{
			callBack(tex);
		}
	}

	public Sprite Load(string spriteName)
	{
		SpriteSheetLoader loader = getLeafLoader(spriteName);
		if(loader == null)
		{
			return null;
		}
		Texture2D tex = loader.getTex();
		Sprite sprite = loader.realLoad (spriteName, tex);
		return sprite;
	}

	public void LoadAsync(string spriteName, AsyncCallBack<Sprite> callBack)
	{
		SpriteSheetLoader loader = getLeafLoader(spriteName);
		if(loader == null)
		{
			callBack(null);
			return;
		}

		loader.getTexAsync((tex2)=>
		{
			if(tex2 == null)
			{
				callBack(null);
				return;
			}

			Sprite sprite = loader.realLoad (spriteName, tex2);
			callBack(sprite);
		});

	}
}



public class SpriteSheetLoaderEx
{
    private float pixelsPerUnit;
    private Dictionary<string, SpriteInfo> spriteInfoMap = new Dictionary<string, SpriteInfo>();
    private string scriptPath;
    private System.WeakReference script;
    private int validMask;
    Dictionary<string, int> spriteMap;

    public SpriteSheetLoaderEx(string scriptPath)
    {
        this.scriptPath = scriptPath;
    }


    public void Init(SpriteSheetInfoScript script)
    {
        if (spriteMap != null)
        {
            return;
        }

        if (script == null)
        {
            return;
        }

        spriteMap = new Dictionary<string, int>();

        for (int i = 0; i < script.sprites.Length; i++)
        {
            if (script.sprites[i] == null)
            {
                Debug.LogError("SpriteSheetInfoScript's sprites is null: " + script.name);
            }
            else
            {
                spriteMap.Add(script.sprites[i].name, i);
            }
        }
    }

    public Sprite GetSprite(string spriteName, SpriteSheetInfoScript script)
    {
        int index = 0;
        Sprite sprite = null;
        if (spriteMap.TryGetValue(spriteName, out index))
        {
            sprite = script.sprites[index];
        }
        return sprite;
    }
            
    public void Read(byte[] data)
    {
        MemoryStream stream = new MemoryStream(((SpriteSheetInfoScript)script.Target).config.bytes);
        BinaryReader reader = new BinaryReader(stream);
        Read(reader);
    }

    public void Read(BinaryReader reader)
    {
        if (spriteInfoMap.Count > 0)
        {
            return;
        }

        string texPath = reader.ReadString();
        pixelsPerUnit = reader.ReadSingle();

        spriteInfoMap.Clear();
        int count = reader.ReadInt32();
        for(int i = 0; i < count; i++)
        {
            string name = reader.ReadString();
            SpriteInfo spriteInfo = new SpriteInfo();
            spriteInfo.Read(reader);
            spriteInfoMap[name] = spriteInfo;
        }
    }

    SpriteEx realLoad (string spriteName, SpriteSheetInfoScript script)
    {
        SpriteEx spriteEx = null;
        string realSpriteName = spriteName.Replace ('/', ResourcesEx.dirSymbol);
        SpriteInfo info = new SpriteInfo ();
        if (spriteInfoMap.TryGetValue (realSpriteName, out info))
        {
            System.WeakReference spriteRef = info.spriteRef;
            if (spriteRef != null)
            {
                spriteEx = spriteRef.Target as SpriteEx;
            }

            if (spriteEx == null || spriteEx.sprite == null || spriteEx.sprite.texture == null)
            {
                Sprite sprite = GetSprite(realSpriteName, script);

//                sprite = Sprite.Create (tex.tex, info.rect, info.pivod, pixelsPerUnit, 0, SpriteMeshType.FullRect, info.border);
//                sprite.name = realSpriteName;

                if (sprite != null)
                {
                    spriteEx = new SpriteEx();
                    spriteEx.sprite = sprite;
                    spriteEx.sheetInfo = script;
                    spriteEx.spriteInfo = info;

                    if (spriteRef == null)
                    {
                        spriteRef = new System.WeakReference (spriteEx);
                    }
                    else
                    {
                        spriteRef.Target = spriteEx;
                    }
                    info.spriteRef = spriteRef;
                }
            }

            return spriteEx;
        }

        return null;
    }

    SpriteEx realLoad2 (string spriteName)
    {
        SpriteEx spriteEx = null;
        string realSpriteName = spriteName.Replace ('/', ResourcesEx.dirSymbol);
        SpriteInfo info = new SpriteInfo ();
        if (spriteInfoMap.TryGetValue (realSpriteName, out info))
        {
            System.WeakReference spriteRef = info.spriteRef;
            if (spriteRef != null)
            {
                spriteEx = spriteRef.Target as SpriteEx;
            }
                
            return spriteEx;
        }

        return null;
    }

    public SpriteInfo GetSpriteInfo(string spriteName, bool isPath = false)
    {
        if(isPath)
        {
            spriteName = spriteName.Replace('/', ResourcesEx.dirSymbol);
        }
        SpriteInfo info;
        spriteInfoMap.TryGetValue(spriteName, out info);
        return info;
    }

//    private SpriteSheetLoaderEx getLeafLoader(string spriteName)
//    {
//        string realSpriteName = spriteName.Replace ('/', ResourcesEx.dirSymbol);
//
//        SpriteInfo info;
//        if(!this.spriteInfoMap.TryGetValue(realSpriteName, out info))
//        {
//            return null;
//        }
//
//        return this;
//    }

    SpriteSheetInfoScript getScript()
    {
        bool reload = script == null || !script.IsAlive;
        if (reload)
        {
            SpriteSheetInfoScript s = ResourceManager.LoadAsset(scriptPath) as SpriteSheetInfoScript;
            if (s == null)
            {
                throw new System.Exception("load script return failed: " + scriptPath);
            }

            if (script == null)
            {
                script = new System.WeakReference(s);
            }
            else
            {
                script.Target = s;
            }
                
            Read(s.config.bytes);
            Init(s);
        }
        return (SpriteSheetInfoScript)script.Target;
    }

    void getScriptAsync(AsyncCallBack<SpriteSheetInfoScript> callBack)
    {
        if(script == null || (script.Target as SpriteSheetInfoScript) == null)
        {
            ResourceManager.LoadAssetAsync(scriptPath, (asset, path)=>
            {
                if (asset == null)
                {
                    throw new System.Exception("load script async return failed: " + scriptPath);
                }

                SpriteSheetInfoScript s = asset as SpriteSheetInfoScript;
                if (script == null)
                {
                    script = new System.WeakReference(s);
                }
                else
                {
                    script.Target = s;
                }
                
                Read(s.config.bytes);
                Init(s);

                callBack(s);
            });
        }
        else
        {
            callBack(script.Target as SpriteSheetInfoScript);
        }
    }

    public SpriteEx Load(string spriteName)
    {
        SpriteSheetLoaderEx loader = this;
        if(loader == null)
        {
            return null;
        }
        SpriteSheetInfoScript script = loader.getScript();
        SpriteEx sprite = loader.realLoad (spriteName, script);
        return sprite;
    }

    public void LoadAsync(string spriteName, System.Action<SpriteEx> callBack)
    {
        SpriteSheetLoaderEx loader = this;
        if(loader == null)
        {
            callBack(null);
            return;
        }

        SpriteEx spriteEx = loader.realLoad2(spriteName);
        if (spriteEx != null && spriteEx.sprite != null && spriteEx.sprite.texture != null)
        {
            callBack(spriteEx);
            return;
        }

        loader.getScriptAsync((script)=>
            {
                if(script == null)
                {
                    callBack(null);
                    return;
                }
                    
                SpriteEx sprite = loader.realLoad (spriteName, script);
                callBack(sprite);
            });
    }
}


